<!--
  对q-select的简单封装，不需要写麻烦的filter，只要传进来options，就可自动生效
  支持的属性有：
  * option-label               - QSelect 的 option-label
  * option-value               - QSelect 的 option-value
  * dense                      - QSelect 的 dense
  * options-dense              - QSelect 的 options-dense
  * options                    - 对于非服务器端数据源，用它来指定选项列表；服务器端数据源，如使用动态服务器端数据，此属性表示初始选项
  * modelValue(v-model)        - v-model的值
  * error                      - QSelect 的 error
  * error-message              - QSelect 的 error-message
  * use-filter                 - 是否启用输入过滤
  * input-debounce             - QSelect 的 input-debounce 输入过滤触发延迟时间
  * search-label-and-value     - 不只是搜索label，还包括value，默认false
  * readonly                   - QSelect 的 readonly
  * disable                    - QSelect 的 disable
  * showNoOption               - 是否显示QSelect默认的无选项插槽
  * auto-select-single-option  - 如果只有1个选项，自动选中，对服务器端动态数据源无效
  * auto-select-first-option   - 如果有1个或以上选项，自动选中第一个选项，对服务器端动态数据源无效
  * trigger-auto-sel-every-ops-chg - 每当选项发生变化时，都尝试触发auto-select，而不只是第一次触发
  * clearable                  - 是否显示清除按钮
  * allow-raw-text             - 可以任意输入字符，而不一定是指定列表中的值
  * styles                     - QSelect 的 style

  * server-side-url            - 获取服务器端下拉数据的URL(不包括参数)
  * search-content-field       - 输入框输入内容要写入请求的哪个参数去
  * resp-data-field            - 返回的数据列表在哪个参数中
  * req-params                 - 请求体的其它参数，如pageNo、pageSize等

  支持的事件有：
  * update:modelValue(v-model) - v-model的事件

  支持的方法有:
  * focus                      - QSelect的focus方法
  * blur                       - QSelect的blur方法
-->
<template>
  <q-select ref='theSelect' :hide-bottom-space="true" :option-label="props.optionLabel"
    :option-value="props.optionValue" :fill-input="props.useFilter" :hide-selected="props.useFilter"
    :options="realOptions" @filter="onFilter" :clearable="props.clearable" :dense="props.dense" emit-value map-options
    :readonly="props.readonly" :disable="props.disable" :input-debounce="props.inputDebounce"
    :options-dense="props.optionsDense" outlined square stack-label transition-hide="none" transition-show="none"
    :use-input="props.useFilter" v-model="realValue" :error="props.error" :error-message="props.errorMessage"
    no-error-icon :style="realStyle" @input-value="onSelectInput" behavior="menu" autocomplete="off">
    <template v-slot:no-option v-if="showNoOption">
      <q-item>
        <q-item-section class="text-grey">
          {{$t('结果为空')}}
        </q-item-section>
      </q-item>
    </template>
  </q-select>
</template>

<script lang="ts">
export default {
  name: 'simpleFilterSelect'
}
</script>
<script lang="ts" setup>
import _ from 'lodash'
import {computed, onMounted, reactive, ref, watch} from 'vue'
import {QSelect} from 'quasar'

interface PropsDef {
  // 允许保留任意文本，默认false
  allowRawText?: boolean,
  // 样式，默认 ''
  styles?: string,
  // 是否显示清除按钮，默认true
  clearable?: boolean,
  // Label如何显示，默认 'label'
  optionLabel?: ((item: any) => string) | string,
  // 需要拿的值，默认’value'
  optionValue?: ((item: any) => any) | string,
  // 选项
  options: any[]
  // 采用紧凑风格，默认true
  dense?: boolean
  // 选项采用紧凑风格，默认true
  optionsDense?: boolean
  // 传递的v-model，对多选而言，必须是Array(这个组件目前不支持多选)
  modelValue: any
  // 错误标识，默认false
  error?: boolean,
  // 错误信息，默认''
  errorMessage?: string,
  // 只读状态，默认false
  readonly?: boolean,
  // 禁用状态，默认false
  disable?: boolean,
  // 使用过滤，默认true
  useFilter?: boolean,
  // 指定它表示使用服务器端过滤，不指定表示不使用服务器端过滤，默认''
  serverSideUrl?: string
  // 将输入的字符串作为一个查询条件传递给服务器，这里用来指定这个查询条件的名称，默认是'searchContent'
  searchContentField?: string,
  // 返回的数据字段是哪个，默认''表示返回的对象本身就是数据列表
  respDataField?: string,
  // 跟服务器端请求的默认参数(不包含searchContentField),默认是：{ pageNo: 1, pageSize: 10 }
  reqParams?: Record<string, any>,
  // 输入保护性延迟，默认200(毫秒)
  inputDebounce?: number,
  // 搜索label和value，而不是只是搜索label，默认false
  searchLabelAndValue?: boolean,
  // 自动选中唯一的选项，默认false
  autoSelectSingleOption?: boolean,
  // 自动选中第一个选项，默认false
  autoSelectFirstOption?: boolean,
  // 每当选项发生变化时，都尝试触发auto-select-xxx，而不只是第一次触发，默认false
  triggerAutoSelEveryOpsChg?: boolean
  // 是否显示QSelect自带的无选项提示(目前就BFS提了这个)
  showNoOption?: boolean
}

const props = withDefaults(defineProps<PropsDef>(), {
  allowRawText: false,
  styles: '',
  clearable: true,
  optionLabel: 'label',
  optionValue: 'value',
  dense: true,
  optionsDense: true,
  error: false,
  errorMessage: '',
  readonly: false,
  disable: false,
  useFilter: true,
  serverSideUrl: '',
  searchContentField: 'searchContent',
  respDataField: '',
  reqParams: () => {
    return {pageNo: 1, pageSize: 10}
  },
  inputDebounce: 200,
  searchLabelAndValue: false,
  autoSelectSingleOption: false,
  autoSelectFirstOption: false,
  triggerAutoSelEveryOpsChg: false,
  showNoOption: false
})

const $d = reactive({
  // 过滤后的选项
  filterOptions: null as (any[]|null),
  // 从服务器返回的选项列表
  optionsFromServerSide: null as (any[]|null)
})

// 首次选项列表赋值标志
const firstOptionsAssigned = ref(true)

// 对于使用服务器数据源的情况，此属性表示是否初始显示，从服务器获取到列表数据后，此值变为false
const isInitDisplay = ref(true)

const emit = defineEmits<{
  (e: 'update:modelValue', data: any): void
}>()

// 输入事件触发
const onSelectInput = function (val: string) {
  if (props.allowRawText) {
    emit('update:modelValue', val)
  }
}

// QSelect组件
const theSelect = ref<QSelect | null>(null)

const useServerSideFilter = computed(() => !!props.serverSideUrl)

// 从服务器端获取下拉列表
const getDropdownListData = function (searchContent: string) {
  let req = {} as Record<string, any>
  _.assign(req, props.reqParams)
  req[props.searchContentField] = searchContent
  return $f.axios.get<Record<string, any> | Record<string, any>[]>(props.serverSideUrl, {
    params: req
  })
    .then((resp) => {
      let lstData: any[]
      if (props.respDataField) {
        lstData = (resp.data as Record<string, any>)[props.respDataField] as any[]
      } else {
        lstData = resp.data as any[]
      }
      if (_.isArray(lstData)) {
        $d.optionsFromServerSide = lstData
        isInitDisplay.value = false
      } else {
        console.warn(props.serverSideUrl + '返回数据有误(非数组)')
      }
      return Promise.resolve()
    })
    .catch((err) => {
      console.error(props.serverSideUrl + '获取下拉数据出错: ' + $f.getAxiosErrMsg(err))
      return Promise.reject()
    })
}

const onFilter = function (inputValue: string, update: (callback?: () => void, after?: (refComp: QSelect) => void) => void, abort: () => void): void {
  if (useServerSideFilter.value) {
    getDropdownListData(inputValue)
      .then(() => {
        update()
      })
      .catch(() => {
        abort()
      })
  } else {
    update(() => {
      if (!props.useFilter) {
        return false
      }
      let inputText = inputValue.trim().toLowerCase()
      $d.filterOptions = _.filter(props.options, (o) => {
        let optionLabel = (theSelect.value?.getOptionLabel(o) ?? '').toString()
        if (optionLabel.toLowerCase().indexOf(inputText) !== -1) {
          return true
        }
        if (props.searchLabelAndValue) {
          let optionValue = (theSelect.value?.getOptionValue(o) ?? '').toString()
          if (optionValue.toLowerCase().indexOf(inputText) !== -1) {
            return true
          }
        }
        return false
      })
    },
      refComp => {
      if (inputValue !== '' && refComp.options && refComp.options.length > 0) {
        refComp.setOptionIndex(-1)
        refComp.moveOptionSelection(1, true)
      }
    })
  }
}

// 检查并选中唯一选项或第一个选项
const checkAndSetTheFirstOrDefaultOption = function () {
  if (
    !useServerSideFilter.value &&
    ((props.autoSelectSingleOption && props.options.length === 1) ||
      (props.autoSelectFirstOption && props.options.length >= 1))
  ) {
    emit('update:modelValue', theSelect.value?.getOptionValue(props.options[0]))
  }
}

// 真值
const realValue = computed({
  get() {
    return props.modelValue
  },
  set(val: any) {
    emit('update:modelValue', val)
  }
})

const realStyle = computed(() => {
  const style = {
    'white-space': 'nowrap',
    'overflow': 'hidden',
    'text-overflow': 'ellipsis'
  } as Record<string, string>
  _.assign(style, props.styles)
  return style
})

const realOptions = computed(() => (useServerSideFilter.value
  ? isInitDisplay.value
    ? props.options
    : $d.optionsFromServerSide
  : ($d.filterOptions ?? props.options) ?? [])
)

// 响应pageNo的变化
watch(() => props.options,
  () => {
    if (props.triggerAutoSelEveryOpsChg || firstOptionsAssigned.value) {
      checkAndSetTheFirstOrDefaultOption()
    }
    firstOptionsAssigned.value = false
    $d.filterOptions = null
  })

// mounted的时候尝试处理默认值
onMounted(() => {
  checkAndSetTheFirstOrDefaultOption()
})

const focus = ()=>theSelect.value?.focus()

const blur = ()=>{
  theSelect.value?.focus()
  theSelect.value?.hidePopup()
}

defineExpose({
  focus,
  blur
})

</script>
